package com.chinasoftinc.facommon;

import ohos.ace.ability.AceInternalAbility;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;
import ohos.rpc.IRemoteObject;
import ohos.rpc.MessageOption;
import ohos.rpc.MessageParcel;
import ohos.rpc.RemoteException;
import ohos.utils.zson.ZSONObject;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 消息远程服务处理
 *
 * @since 2022-05-16
 */
public class EventInternalAbility extends AceInternalAbility {
    private static final int SUBSCRIBE_INFO = 2000;
    private static final int SUBSCRIBE = 2001;
    private static final int UNSUBSCRIBE = 2002;
    private static final int PUBLISH_EVENT = 2003;

    private static final String TAG = "EventInternalAbility";

    private final ConcurrentHashMap<String, EventObserver> observers = new ConcurrentHashMap<>(10);
    private EventObserver subscribeObject;

    private EventInternalAbility() {
        super(EventInternalAbility.class.getPackage().getName(), EventInternalAbility.class.getName());
    }

    public static EventInternalAbility get() {
        return InnerClass.INSTANCE;
    }

    public void startServer() {
        setInternalAbilityHandler(this::onRemoteRequest);
    }

    private static final class InnerClass {
        private static final EventInternalAbility INSTANCE = new EventInternalAbility();
    }

    private boolean onRemoteRequest(int code, MessageParcel data, MessageParcel reply, MessageOption option) {
        LogUtil.info(TAG, "onRemoteRequest" + code);
        switch (code) {
            case SUBSCRIBE_INFO:
                try {
                    String text = data.readString();
                    SubscribeMessage subscribeInfo = ZSONObject.stringToClass(text, SubscribeMessage.class);
                    if (subscribeInfo != null && !isEmpty(subscribeInfo.getKey())) {
                        subscribeObject = new EventObserver(subscribeInfo.getKey(), subscribeInfo.getSubscribeEventType());
                        responseSuccess(reply);
                    } else {
                        responseFailed(reply, -1, "The subscribe key cannot be empty");
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    responseFailed(reply, -2, e.getMessage());
                }
                return true;
            case SUBSCRIBE:
                if (subscribeObject != null) {
                    subscribeObject.setRemoteObject(data.readRemoteObject());
                    observers.put(subscribeObject.getKey(), subscribeObject);
                    responseSuccess(reply);
                } else {
                    responseFailed(reply, -1, "Invalid subscribe info");
                }
                return true;
            case UNSUBSCRIBE:
                SubscribeMessage unsubscribeInfo = null;
                try {
                    unsubscribeInfo = ZSONObject.stringToClass(data.readString(), SubscribeMessage.class);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                if (unsubscribeInfo != null && !isEmpty(unsubscribeInfo.getKey())) {
                    observers.remove(unsubscribeInfo.getKey());
                    responseSuccess(reply);
                    return true;
                } else {
                    responseFailed(reply, -2, "Invalid params");
                    return false;
                }
            case PUBLISH_EVENT:
                String publishJson = data.readString();
                try {
                    PublishMessage publishMessage = ZSONObject.stringToClass(publishJson, PublishMessage.class);
                    if (publishMessage != null && !isEmpty(publishMessage.getType())) {
                        observers.forEach((key, eventObserver) -> eventObserver.handleEvent(publishMessage));
                        responseSuccess(reply);
                    } else {
                        responseFailed(reply, -3, "The params type cannot be empty");
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    responseFailed(reply, -2, e.getMessage());
                }
                break;
        }
        return false;
    }

    private void responseFailed(MessageParcel reply, int code, String msg) {
        Map<String, Object> dataMap = new HashMap<>();
        dataMap.put("code", code);
        dataMap.put("msg", msg);
        reply.writeString(ZSONObject.toZSONString(dataMap));
    }

    public static void responseSuccess(MessageParcel reply) {
        try {
            reply.writeString("{\"code\":0}");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void stopServer() {
        setInternalAbilityHandler(null);
    }

    boolean isEmpty(String value) {
        return value == null || value.length() == 0;
    }

    static class PublishMessage {
        private String type;
        private String data;
        private int code;

        public String getType() {
            return type;
        }

        public void setType(String type) {
            this.type = type;
        }

        public String getData() {
            return data;
        }

        public void setData(String data) {
            this.data = data;
        }

        public int getCode() {
            return code;
        }

        public void setCode(int code) {
            this.code = code;
        }
    }

    static class SubscribeMessage {
        private String key;
        private List<String> subscribeEventType;

        public String getKey() {
            return key;
        }

        public void setKey(String key) {
            this.key = key;
        }

        public List<String> getSubscribeEventType() {
            return subscribeEventType;
        }

        public void setSubscribeEventType(List<String> subscribeEventType) {
            this.subscribeEventType = subscribeEventType;
        }
    }

    static class EventObserver {
        private IRemoteObject remoteObject;
        private List<String> subscribeEventType;
        private String key;

        public EventObserver(String key, List<String> subscribeEvents) {
            this.key = key;
            this.subscribeEventType = subscribeEvents;
        }

        public void setRemoteObject(IRemoteObject remoteObject) {
            this.remoteObject = remoteObject;
        }

        public String getKey() {
            return key;
        }

        void handleEvent(PublishMessage message) {
            if (message != null) {
                if (subscribeEventType == null || subscribeEventType.contains(message.getType())) {
                    MessageParcel data = MessageParcel.obtain();
                    MessageParcel reply = MessageParcel.obtain();
                    MessageOption option = new MessageOption();
                    data.writeString(ZSONObject.toZSONString(message));
                    if (remoteObject != null) {
                        try {
                            remoteObject.sendRequest(0, data, reply, option);
                        } catch (RemoteException e) {
                            LogUtil.error(TAG, "failed to send data to js");
                        }
                    }
                    reply.reclaim();
                    data.reclaim();
                }
            }
        }
    }

    static class LogUtil {
        private static final HiLogLabel LABEL_LOG = new HiLogLabel(HiLog.DEBUG, 0xD000F00, "Event");

        public static void info(String tag, String msg) {
            HiLog.info(LABEL_LOG, tag, msg);
        }

        public static void error(String tag, String msg) {
            HiLog.error(LABEL_LOG, tag, msg);
        }
    }
}
